/**
 * ! Does not support high memory which is not a multiple of 4k (is this even a problem?)
 */

#include <pmm.h>
#include <debug.h>
#include <bitmap.h>
#include <string.h>
#include <linkedlist.h>
#include <kmalloc.h>

size_t g_highmem;
size_t g_total_frames;
//static byte *phys_bitmap;
//static dword phys_bitmap_length;

void *kend;  // end of kernel space
void *aligned_kend;  // end of kernel space aligned to 4K boundary
void *mapping_start;  // beginning of physical frame mapping

static dword region_count;
static slinkedlist_t *frames;
static slinkedlist_t *frames_head;

/**
 * Initialize the physical memory (frame) allocator
 * 
 * void *free - start of free memory (end of the kernel image, incl. bss)
 * size_t size - size of memory (amount of total high mem)
 *   - this will always be 4k divisible
 */
int init_pmm(void *free, size_t size)
{
	int ret = 0;
	
	kend = free;
	g_highmem = size;
	
	debugf("[PMM] Init 0x%08x - 0x%08x\n", free, size);
	
	g_total_frames = size / 0x1000;
	
	/*
	 * not using bitmaps or stacks anymore.
	 
	// TODO: use two stacks or stack+bitmap to divide up DMA area (<16mb) and other mem
	
	// get the total number of 4K frames
	phys_bitmap_length = DIV_UP(total_frames, (sizeof(byte) * 8));
	
	// put a bitmap at the beginning of free mem and adjust the freemem ptr
	phys_bitmap = (byte*)free;
	dwzero(phys_bitmap, phys_bitmap_length);
	kend = free + phys_bitmap_length;
	
	// mark frames up to (free) as used
	int i;
	for (i = 0; i < DIV_UP((dword)kend, 0x1000); i++)
	{
		SET_BIT(phys_bitmap, i);
	}
	*/
	
	
	// align to 4K and count number of frames
	aligned_kend = (void*)ALIGN_UP((dword)kend, 0x1000);
	region_count = DIV_DOWN(g_highmem - (dword)aligned_kend, 0x1000);
	
	// one linkedlist entry for each frame, possible 8Mb of space for 4Gb at 8 bytes per entry
	debugf("[PMM] %u bytes of memory to address, %u frames\n", g_highmem - (dword)aligned_kend, region_count);
	
	frames = kend;
	mapping_start = aligned_kend;
	
	kend = kend + (sizeof(slinkedlist_t) * region_count);
	aligned_kend = (void*)ALIGN_UP((dword)kend, 0x1000);
	
	int i;
	for (i = 0; i < region_count; i++)
	{
		if ((mapping_start + (i*4096)) > kend)  // dont map frames which include the <frames> array
		{
			frames[i].next = &frames[i+1];
			frames[i].node = mapping_start + (i * 4096);
		}
		else
		{
			frames[i].next = NULL;
			frames[i].node = 0;
		}
	}
	frames[region_count-1].next = NULL;  // fix last entry
	
	i = 0;
	while (frames[i].node == 0)
		i++;
	
	frames_head = &frames[i];
	
	return ret;
}

/**
 * Allocate a physical memory frame
 * 
 * size_t framesize - number of frames to allocate, will try to allocate contiguous frames
 * 
 * returns - void* to first frame, or NULL on error or no <frames> contiguous frames
 */
void * pmm_frame_alloc(size_t framesize)
{
	void *ret;
	
	if (frames_head == NULL)
	{
		debugf("[PMM] No free physical frames\n");
		return NULL;
	}
	
	if (framesize > 1)
	{
		// not supported right now
		debugf("[PMM] More than 1 frame at a time is not supported\n");
		return NULL;
	}
	
	ret = frames_head->node;
	frames_head = frames_head->next;
	
	//debugf("[PMM] Allocating 0x%08x, base is 0x%08x\n", ret, frames);
	
	return ret;
}

/**
 * Free a physical memory frame
 * 
 * void *ptr - ptr to frame(s) to free
 */
void pmm_frame_free(void *ptr)
{
	// find the entry with the corresponding address
	if ((dword)ptr & (0x1000-1))
	{
		// not a valid aligned frame
		debugf("[PMM] Invalid frame to free 0x%08x\n", ptr);
		return;
	}
	
	dword index;
	index = (ptr - mapping_start) / 4096;
	slinkedlist_t *frame = &frames[index];
	frame->next = frames_head;
	frames_head = frame;
	
	debugf("[PMM] Freed frame #%d\n", index);
	
}
